package assignment4;

import java.util.ArrayList;
import java.util.Random;

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */

/**
 *
 * @author chrisjaramillo
 */
public class NeuralNetwork {
    ArrayList m_inputNodes;
    ArrayList m_hiddenNodes;
    ArrayList m_outputNodes;
    ArrayList m_trainingInput;
    ArrayList m_trainingOutput;
    ArrayList m_epochOutput;
    double m_learningRate;
    double m_theta;
    
    NeuralNetwork()
    {
        m_inputNodes = new ArrayList();
        m_hiddenNodes = new ArrayList();
        m_outputNodes = new ArrayList();
        m_trainingInput = new ArrayList();
        m_trainingOutput = new ArrayList();
        m_epochOutput = new ArrayList();
        m_learningRate = 0.0;
        m_theta = 0.0;
    }
    
    public void setInputNeuronCount(int count)
    {
        for (int i=0; i<count; i++)
        {
            m_inputNodes.add(new InputNode(this));
        }
    }
    
    public void setHiddenNeuronCount(int count)
    {
        for (int i=0; i<count; i++)
        {
            m_hiddenNodes.add(new HiddenNode(this));
        }
    }
    
    public void setOutputNeuronCount(int count)
    {
        for (int i=0; i<count; i++)
        {
            m_outputNodes.add(new OutputNode(this));
        }
    }
    
    public void generateTrainingValues(int count)
    {
        Random random = new Random();
        
        for(int i=0; i<count; i++)
        {
            double value = random.nextDouble();
            value *= 5.0;
            m_trainingInput.add(value);
            m_trainingOutput.add(calculateYfromX(value));
        }
        double value = random.nextDouble();
        double range = (2.4/m_outputNodes.size())*2.0;
        value *= range;
        value -= range/2.0;
        m_theta = value;
    }
    
    private double calculateYfromX(double input)
    {
        double output;
        output = Math.pow(input, 3) + 2*Math.pow(input, 2) - 2*input +1;
        return output;
    }
    
    public void initialize()
    {
        m_inputNodes.stream().forEach((inputNode) -> {
            ((InputNode)inputNode).setRandomWeights();
        });
        
        m_hiddenNodes.stream().forEach((hiddenNode) -> {
            ((HiddenNode)hiddenNode).setRandomWeights();
        });
        
        m_outputNodes.stream().forEach((outputNode) -> {
            ((OutputNode)outputNode).setRandomWeights();
        });
    }
    
    public ArrayList inputNodes()
    {
        return m_inputNodes;
    }
    
    public ArrayList hiddenNodes()
    {
        return m_hiddenNodes;
    }
    
    public ArrayList outputNodes()
    {
        return m_outputNodes;
    }
    
    public void activate()
    {
        for(int i=0; i<m_trainingOutput.size(); i++)
        {
            this.setInputNeuronValues((double)m_trainingOutput.get(i));
            this.setHiddenNeuronValues();
            this.setOutputNeuronValues();
            this.calculateOutput();
        }
    }
    
    private void calculateOutput()
    {
        double value = 0.0;
        for(int i=0; i<m_outputNodes.size(); i++)
        {
            OutputNode node = (OutputNode)m_outputNodes.get(i);
            double outputNodeVal = node.value();
            double weight = node.weight();
            value += outputNodeVal * weight - m_theta;
        }
        value = 1/(1+Math.pow(Math.E,(-1 * value)));
        System.out.println("Output Value: " + value);
    }
    
    public void train()
    {
        double error = 0.0;
        int count = 0;
        while(count < 300)
        {       
            count++;
        }
    }
    
    private void setInputNeuronValues(double inputVal)
    {
        for(int i=0; i<m_inputNodes.size(); i++)
        {
            ((InputNode)m_inputNodes.get(i)).value(inputVal);
        }
    }
    
    private void setHiddenNeuronValues()
    {
        for(int i=0; i<m_hiddenNodes.size(); i++)
        {
            ((HiddenNode)m_hiddenNodes.get(i)).value(i);
        }
    }
    
    private void setOutputNeuronValues()
    {
        for(int i=0; i<m_outputNodes.size(); i++)
        {
            ((OutputNode)m_outputNodes.get(i)).value(i);
        }
    }
}

